/*
 * Copyright 2006-2007 S W McIlroy
 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 * you may not use  this file  except in  compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed  under the  License is distributed on an "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 * implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mockfu;

import static java.util.concurrent.TimeUnit.*;
import static org.mockfu.Mocks.*;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;

import org.testng.annotations.Test;

public class MockControlTest
{

    private int foo;

    public class ObjectWithPrivateCtor
    {
        private ObjectWithPrivateCtor()
        {
            foo = 10;
        }

        public void foo()
        {
            foo = 11;
        }
    }

    @Test
    public void constructorNotInvoked()
    {
        foo = 111;

        MockControl<ObjectWithPrivateCtor> bar = MockControl.of( ObjectWithPrivateCtor.class );
        bar.expectsOnce().foo();
        bar.mock().foo();
        bar.verify();

        assertThat( foo, eq( 111 ) );

        new ObjectWithPrivateCtor();
        assertThat( foo, eq( 10 ) );
    }

    @SuppressWarnings( "serial" )
    public class SerializableWithoutUID implements Serializable
    {
        public SerializableWithoutUID()
        {
            foo = 20;
        }

        public void foo()
        {
            foo = 21;
        }
    }

    @Test
    public void serializableMock()
    {
        foo = -3;

        MockControl<SerializableWithoutUID> qaz = MockControl.of( SerializableWithoutUID.class );
        qaz.expectsOnce().foo();
        qaz.mock().foo();
        qaz.verify();

        assertThat( foo, eq( -3 ) );

        new SerializableWithoutUID();
        assertThat( foo, eq( 20 ) );
    }

    @Test
    public void allowsAnything()
    {
        MockControl<List<String>> list = new MockControl<List<String>>() {};

        list.allowsAnything();

        list.stubWith(new ArrayList<String>() {
            private static final long serialVersionUID = 1L;

            @Override
            public String get(int index)
            {
                return "mabuse";
            }
        });

        stub(list.expectsOnce().iterator(), (Iterator<String>)null);

        list.stubWithException(new UnsupportedOperationException());

        assertThat(list.mock().get(1), "mabuse");
        assertThat(list.mock().iterator(), null);

        try
        {
            list.mock().subList(0, 1);
            throw new AssertionError("Expected exception");
        }
        catch (UnsupportedOperationException ex)
        {
        }
    }

    @Test
    public void duplicateAllows()
    {
        MockControl<Calendar> calendar = MockControl.of(Calendar.class);

        final long[] timeInMillis = { 0 };

        calendar.allowsAnything();
        calendar.stubWith(new GregorianCalendar() {
            private static final long serialVersionUID = 1L;

            @Override
            public void setTimeInMillis(long millis)
            {
                timeInMillis[0] = millis;
            }
        });

        calendar.allows().setTimeInMillis(100);
        calendar.stubWithException(new UnsupportedOperationException());


        try
        {
            calendar.mock().setTimeInMillis(100);
            throw new AssertionError("Expected exception");
        }
        catch (UnsupportedOperationException ex)
        {
        }

        assertThat(timeInMillis[0], not(100l));

        calendar.mock().setTimeInMillis(200);
        assertThat(timeInMillis[0], 200l);
    }

    @Test( expectedExceptions = FileNotFoundException.class )
    public void invokeConstructor()
    {
        /* create a FileInputStream for a non-existent file */
        MockControl<FileInputStream> list = MockControl.of( FileInputStream.class );
        list.invokeConstructor( new Class[] { String.class }, "" );
    }

    @Test
    public void invokeConstructorWithExpectations()
    {
        MockControl<Throwable> throwable = MockControl.of( Throwable.class );
        throwable.expectsOnce().fillInStackTrace();

        throwable.invokeConstructor();

        throwable.verify();
    }

    @Test
    public void unexpectedMockArgument()
    {
        /* tests what happens when a mock object is passed as an argument to an unexpected method */
        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
        try
        {
            map.mock().get( map.mock() );
        }
        catch( ExpectationFailure ex )
        {
            assertThat( ex.getMessage(), matches( ".*get\\(.*" ) );
            return;
        }
        throw new AssertionError();
    }

    @Test
    public void mockArgument()
    {
        MockControl<List<String>> list = new MockControl<List<String>>() {};
        stub( list.expects( 2 ).get( 0 ), "" );

        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
        stub( map.expectsOnce().get( list.mock().get( 0 ) ), "" );
        stub( map.expectsOnce().get( "x" ), list.mock().get( 0 ) );
        map.expectsOnce().get( same( list.mock() ) );

        /* no exceptions thrown */
    }

    @Test
    public void mockArgumentExpectations()
    {
        /* test that expectations aren't incremented when a matcher calls methods on a mock */
        final boolean[] equalsCalled = new boolean[1];

        MockControl<CharSequence> seq = MockControl.of( CharSequence.class );
        seq.expects( 0 ).equals( "z" );
        seq.stubWithDelegate( new Object() {
            @Override
            public boolean equals( Object obj )
            {
                equalsCalled[0] = true;
                return true;
            }
        } );

        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
        map.allows().get( "z" );

        map.mock().get( seq.mock() );

        assertThat( equalsCalled[0] );
        seq.verifyAll();
    }

    static class Injectable
    {
        private static Map<String, String> staticMap = new HashMap<String, String>();
        private final Map<String, String> map = new HashMap<String, String>();

        public Injectable()
        {
            map.put( "myrna", "loy" );
            staticMap.put( "marion", "davies" );
        }

        public void add( String key, String value )
        {
            map.put( key, value );
            staticMap.put( key, value );
        }

        public boolean contains( String key )
        {
            return map.containsKey( key );
        }

        public boolean staticContains( String key )
        {
            return staticMap.containsKey( key );
        }
    }

    @Test
    public void injectIntoObject()
    {
        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
        map.expectsOnce().put( "betty", "balfour" );

        Injectable injectable = new Injectable();
        map.injectInto( injectable );

        try
        {
            injectable.add( "betty", "balfour" );

            map.verify();
        }
        finally
        {
            map.eject();
            Injectable.staticMap.clear();
        }
        assertThat( injectable.map.keySet(), contains( and( "myrna", not( "betty" ) ) ) );
    }

    @Test
    public void injectAssignableIntoObject()
    {
        MockControl<HashMap<String, String>> hashMap = new MockControl<HashMap<String, String>>() {};
        hashMap.expectsOnce().put( "betty", "balfour" );

        Injectable injectable = new Injectable();
        hashMap.injectInto( injectable );
        try
        {
            injectable.add( "betty", "balfour" );

            hashMap.verify();
        }
        finally
        {
            hashMap.eject();
            Injectable.staticMap.clear();
        }
        assertThat( injectable.map.keySet(), contains( and( "myrna", not( "betty" ) ) ) );
    }

    @Test
    public void injectIntoClass()
    {
        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
        map.expectsOnce().put( "mary", "astor" );

        Injectable injectable = new Injectable();
        map.injectInto( Injectable.class );

        try
        {
            injectable.add( "mary", "astor" );

            map.verify();
        }
        finally
        {
            map.eject();
        }
        assertThat( Injectable.staticMap.keySet(), eq( new HashSet<String>( Arrays
                .asList( "marion" ) ) ) );

        assertThat( Injectable.staticMap.size(), eq( 1 ) );
        assertThat( Injectable.staticMap.keySet(), contains( "marion" ) );
    }


    @Test(expectedExceptions = IllegalStateException.class)
    public void duplicateInjection()
    {
        MockControl<Map> map = MockControl.of(Map.class);

        Injectable injectable = new Injectable();
        map.injectInto( injectable );
        try
        {
            MockControl.of(Map.class).injectInto( injectable );
        }
        finally
        {
            map.eject();
        }
    }

    @Test(expectedExceptions = IllegalStateException.class)
    public void duplicateStaticInjection()
    {
        MockControl<Map> map = MockControl.of(Map.class);

        map.injectInto( Injectable.class );
        try
        {
            MockControl.of(Map.class).injectInto( Injectable.class );
        }
        finally
        {
            map.eject();
        }
    }

    @Test(timeOut = 5000)
    public void injectMultipleThreads() throws Exception
    {
        MockControl<Map<String, String>> garboMap = new MockControl<Map<String, String>>() {};
        garboMap.expectsOnce().put("greta", "garbo");

        final Injectable injectable = new Injectable();

        ExecutorService executor = Executors.newFixedThreadPool(2);
        final Semaphore signalToBrooks = new Semaphore(0);
        final Semaphore signalFromBrooks = new Semaphore(0);
        final Semaphore signalToBow = new Semaphore(0);
        final Semaphore signalFromBow = new Semaphore(0);

        Future<?> brooksFuture = executor.submit(new Callable<Object>() {
            public Object call() throws Exception
            {
                assertThat(signalToBrooks.tryAcquire(1, SECONDS));

                MockControl<Map<String, String>> brooksMap = new MockControl<Map<String, String>>() {};
                brooksMap.expectsOnce().put("louise", "brooks");
                brooksMap.injectInto(Injectable.class);

                /* wait for garbo to be used */
                signalFromBrooks.release();
                assertThat(signalToBrooks.tryAcquire(1, SECONDS));

                try
                {
                    injectable.add("louise", "brooks");
                    brooksMap.verify();
                }
                finally
                {
                    /* wait for garbo thread to be tested using original */
                    signalFromBrooks.release();
                    assertThat(signalToBrooks.tryAcquire(1, SECONDS));
                    brooksMap.eject();
                }

                /* field should display original behavior */
                assertThat(Injectable.staticMap.keySet(), equalsSet("marion"));

                return null;
            }
        });

        Future<?> bowFuture = executor.submit(new Callable<Object>() {
            public Object call() throws Exception
            {
                assertThat(signalToBow.tryAcquire(1, SECONDS));

                MockControl<Map<String, String>> bowMap = new MockControl<Map<String, String>>() {};
                bowMap.expectsOnce().put("clara", "bow");
                bowMap.injectInto(Injectable.class);

                signalFromBow.release();
                assertThat(signalToBow.tryAcquire(1, SECONDS));

                try
                {
                    injectable.add("clara", "bow");
                    bowMap.verify();
                }
                finally
                {
                    bowMap.eject();
                }

                /* field should display original behavior */
                assertThat(Injectable.staticMap.keySet(), equalsSet("marion"));

                return null;
            }
        });

        /* inject garbo and allow other threads to begin executing */
        garboMap.injectInto(Injectable.class);
        signalToBrooks.release();
        signalToBow.release();

        /* wait for brooks and bow to be injected */
        assertThat(signalFromBrooks.tryAcquire(1, SECONDS));
        assertThat(signalFromBow.tryAcquire(1, SECONDS));

        try
        {
            injectable.add("greta", "garbo");
            garboMap.verify();
        }
        finally
        {
            /* wait for brooks to be used */
            signalToBrooks.release();
            assertThat(signalFromBrooks.tryAcquire(1, SECONDS));

            garboMap.eject();
        }

        /* field should display original behavior */
        assertThat(Injectable.staticMap.keySet(), equalsSet("marion"));

        /* wait for brooks to be ejected */
        signalToBrooks.release();
        brooksFuture.get(1, SECONDS);

        /* wait for bow to be used and ejected */
        signalToBow.release();
        bowFuture.get(1, SECONDS);

        garboMap.eject();

        /* field should display original behavior */
        assertThat(Injectable.staticMap.keySet(), equalsSet("marion"));

        assertThat(!(Injectable.staticMap instanceof Proxy));
    }

    @Test( expectedExceptions = ExpectationFailure.class )
    public void verifyAll()
    {

        MockControl<List<String>> list1 = new MockControl<List<String>>() {};
        MockControl<List<String>> list2 = new MockControl<List<String>>() {};
        MockControl<List<String>> list3 = new MockControl<List<String>>() {};

        list1.expectsOnce().add( "one" );
        list2.expectsOnce().add( "two" );
        list3.expectsOnce().add( "three" );

        list1.mock().add( "one" );
        list2.mock().add( "two" );

        list1.verifyAll();
    }

    @Test(expectedExceptions=ExpectationFailure.class)
    public void ignoredExpectationFailureVerify() throws Exception
    {
        /* if a failure was ignored (maybe because it was thrown in another thread), verify should throw it */
        MockControl<Reader> reader = MockControl.of(Reader.class);

        try
        {
            reader.mock().close();
            throw new AssertionError("ExpectationFailure should have been thrown");
        }
        catch (ExpectationFailure ignored)
        {
        }

        reader.verify();
    }

    @Test
    public void ignoredExpectationFailure() throws Exception
    {
        /* if a failure was ignored (maybe because it was thrown in another thread),
         * a second failure should bring forward the first */
        MockControl<Reader> reader = MockControl.of(Reader.class);

        try
        {
            reader.mock().close();
            throw new AssertionError("ExpectationFailure should have been thrown");
        }
        catch (ExpectationFailure ignored)
        {
        }

        try
        {
            reader.mock().hashCode();
            throw new AssertionError("ExpectationFailure should have been thrown");
        }
        catch (ExpectationFailure ex)
        {
            assertThat(ex.getMessage(), matches(".*Reader.close\\(\\).*"));
        }
    }

    // @Test
    // public void verifyThread() throws Exception
    // {
    //
    // final MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
    // map.expects( 2 ).put( "foo", "bar" );
    //
    // map.mock().put( "foo", "bar" );
    //
    // new Thread() {
    // public void run()
    // {
    // try
    // {
    // Thread.sleep( 20L );
    // }
    // catch( InterruptedException ex )
    // {
    // Thread.currentThread().interrupt();
    // }
    // map.mock().put( "foo", "bar" );
    // }
    // }.start();
    //
    // map.waitAtMost( 500L );
    // map.verify();
    //
    // }

    @Test
    public void stubReturnValue()
    {
        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
        stub( map.expectsOnce().get( "foo" ), "bar" );

        assertThat( map.mock().get( "foo" ), is( "bar" ) );

        map.verify();
    }

    @Test( expectedExceptions = IllegalStateException.class )
    public void stubDuplicate()
    {
        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
        map.expectsOnce().get( "foo" );

        map.mock().get( "foo" );

        stub( null, "bar" );
        stub( null, "qaz" );
    }

    @Test
    public void stubVoid()
    {
        final StringBuilder s = new StringBuilder();

        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
        map.expectsOnce().put( "foo", "bar" );
        map.stubLastWith( new Stub<Object>() {
            public Object invoke( InvocationContext context ) throws Throwable
            {
                s.append( "qaz" );
                return null;
            }
        } );

        map.mock().put( "foo", "bar" );

        assertThat( s.toString(), "qaz" );
    }

    @Test
    public void stubWith()
    {
        final StringBuilder s = new StringBuilder();

        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
        map.expectsOnce().put( "foo", "bar" );
        map.expectsOnce().clear();

        map.stubWithDelegate( new HashMap<String, String>() {
            private static final long serialVersionUID = 1L;

            @Override
            public String put( String key, String value )
            {
                s.append( "qaz" );
                return null;
            }

            @Override
            public void clear()
            {
                s.append( "!" );
                ignored();
            }

            private void ignored()
            {
            }
        } );

        map.allowsAnything();

        map.mock().put( "foo", "bar" );
        map.mock().put( "bar", "qux" );
        map.mock().clear();

        assertThat( s.toString(), "qaz!" );
    }

    @Test
    public void stubWithDelegate() throws Exception
    {
        MockControl<Reader> reader = MockControl.of( Reader.class );

        BufferedReader delegate = new BufferedReader( new StringReader( "foo" ) );

        reader.allowsAnything();
        reader.stubWithDelegate( delegate );

        char[] buffer = new char[100];
        int n = reader.mock().read( buffer );
        assertThat( n, not( -1 ) );
        assertThat( new String( buffer, 0, n ), "foo" );
    }

    @Test
    public void stubUnmatched()
    {
        final StringBuilder s = new StringBuilder();

        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
        map.expects( 2 ).put( (String) not( null ), any( String.class ) );

        map.stubWithDelegate( new HashMap<String, String>() {
            private static final long serialVersionUID = 1L;

            @Override
            public String put( String key, String value )
            {
                if( key.length() >= 4 )
                    throw new UnmatchedException();
                s.append( value );
                return null;
            }
        } );

        try
        {
            map.mock().put( "foo", "qaz" );
            map.mock().put( "fooz", "!" );
            throw new RuntimeException( "Should have thrown expectation failure" );
        }
        catch( ExpectationFailure ex )
        {
        }

        assertThat( s.toString(), "qaz" );
    }

    @Test
    public void stubMatchCount()
    {
        final StringBuilder s = new StringBuilder();

        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};
        map.expects( 2 ).put( (String) not( null ), any( String.class ) );

        map.stubWithDelegate( new HashMap<String, String>() {
            private static final long serialVersionUID = 1L;

            @Override
            public String put( String key, String value )
            {
                switch( InvocationContext.current().getMatchCount() )
                {
                case 0:
                    s.append( "q" );
                    break;
                case 1:
                    s.append( "u" );
                    break;
                default:
                    s.append( "x" );
                    break;
                }
                return null;
            }
        } );

        map.mock().put( "foo", null );
        map.mock().put( "bar", "" );

        assertThat( s.toString(), "qu" );
    }

    @Test
    public void stubBoundedSubclass()
    {
        final StringBuilder s = new StringBuilder();

        abstract class Base<T extends CharSequence & Serializable>
        {
            abstract void send( T t );
        }

        MockControl<Base<String>> base = new MockControl<Base<String>>() {};
        base.expectsOnce().send( "boo" );

        base.stubWith( new Base<String>() {
            @Override
            void send( String t )
            {
                s.append( "qaz" );
            }
        } );

        base.mock().send( "boo" );

        assertThat( s.toString(), "qaz" );
    }

    @Test
    public void stubSuper()
    {
        MockControl<ArrayList<Object>> list = new MockControl<ArrayList<Object>>() {};
        list.invokeConstructor( new Class[] { Collection.class }, Arrays.asList( 7, 9 ) );

        stub( list.expectsOnce().isEmpty(), true );

        list.expectsOnce().size();
        list.stubWithSuper();

        assertThat( list.mock().isEmpty(), true );
        assertThat( list.mock().size(), 2 );
    }

    @Test
    public void stubAbstractSuper() throws IOException
    {
        MockControl<Reader> reader = MockControl.of( Reader.class );
        reader.invokeConstructor( new Class[0], new Object[0] );
        reader.allowsAnything();
        reader.stubWithSuper();
        stub( reader.expectsOnce().read( any( char[].class ), 0, any( int.class ) ), -1 );
        reader.expectsOnce().close();

        assertThat( reader.mock().read(), -1 );
    }

    @Test( expectedExceptions = SQLException.class )
    public void stubException()
    {

        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};

        map.expectsOnce().put( "foo", "bar" );
        map.stubWithException( new SQLException() );

        map.mock().put( "foo", "bar" );

        throw new AssertionError( "SQLException should have been thrown" );
    }

    @Test
    public void stubExceptionUninvoked()
    {
        MockControl<Map<String, String>> map = new MockControl<Map<String, String>>() {};

        map.expectsOnce().put( "foo", "bar" );
        map.stubWithException( new SQLException() );
        /* this should not throw an exception */
    }

    @Test
    public void stubDelegateMethod()
    throws IOException
    {
        MockControl<Reader> reader = MockControl.of( Reader.class );
        reader.allows().read( any(char[].class) );
        reader.stubWithDelegate( new Reader()
        {
            @Override
            public void close() throws IOException
            {
            }

            @Override
            public int read( char[] cbuf ) throws IOException
            {
                Reader obj = (Reader)InvocationContext.current().getObject();
                return obj.read( cbuf, 0, cbuf.length );
            }

            @Override
            public int read( char[] cbuf, int off, int len ) throws IOException
            {
                return -1;
            }
        });
        stub( reader.expectsOnce().read( any(char[].class), any(int.class), any(int.class) ), 10);

        assertThat(reader.mock().read( new char[1]), 10);

        reader.verify();
    }

    @Test
    @SuppressWarnings("unchecked")
    public void stubWithMethodResult()
    {
        MockControl<List> list = MockControl.of(List.class);
        stub(list.allows().subList(0, 1), getSubList());

        assertThat(list.mock().subList(0, 1).get(5), "stubbed");
    }

    @SuppressWarnings("unchecked")
    private List<?> getSubList()
    {
        MockControl<List> sublist = MockControl.of(List.class);
        stub(sublist.allows().get(5), "stubbed");
        sublist.allows().subList(any(int.class), any(int.class));
        return sublist.mock();
    }

    @Test
    public void inSequence()
    {
        MockControl<Map<String, Integer>> map = new MockControl<Map<String, Integer>>() {};
        map.allowsAnything();

        map.beginSequence();
        map.expectsOnce().put( "one", 1 );
        map.expectsOnce().put( "two", 2 );
        map.expectsOnce().put( "three", 3 );
        map.endSequence();

        map.expectsOnce().put( "two", 2 );

        map.mock().put( "one", 1 );
        map.mock().put( "two", 2 );
        map.mock().put( "one", 1 );
        map.mock().put( "three", 3 );

        map.verify();
    }

    @Test( expectedExceptions = ExpectationFailure.class )
    public void outOfSequence()
    {
        MockControl<Map<String, Integer>> map = new MockControl<Map<String, Integer>>() {};
        map.allowsAnything();

        map.beginSequence();
        map.expectsOnce().put( "one", 1 );
        map.expectsOnce().put( "two", 2 );
        map.expectsOnce().put( "three", 3 );
        map.endSequence();

        map.mock().put( "two", 2 );
        map.mock().put( "one", 1 );
        map.mock().put( "three", 3 );

        map.verify();
    }

    @Test
    public void stubSequence()
    {
        MockControl<Map<String, Integer>> map = new MockControl<Map<String, Integer>>() {};

        map.beginSequence();
        stub( map.expectsOnce().put( "", null ), 1 );
        stub( map.expects( 2 ).put( "", null ), 2 );
        stub( map.expectsOnce().put( "", null ), 3 );
        map.endSequence();

        assertThat( map.mock().put( "", null ), 1 );
        assertThat( map.mock().put( "", null ), 2 );
        assertThat( map.mock().put( "", null ), 2 );
        assertThat( map.mock().put( "", null ), 3 );

        map.verify();
    }

    @Test
    public void variableSequence()
    {
        MockControl<List<String>> list = new MockControl<List<String>>() {};

        list.beginSequence();
        list.expectsOnce().add( "foo" );
        list.allows().add( "bar" );
        list.expectsAtLeast( 1 ).add( "qaz" );
        list.allows().add( "qux" );
        list.endSequence();

        list.mock().add( "foo" );
        list.mock().add( "bar" );
        list.mock().add( "bar" );
        list.mock().add( "qaz" );
        list.mock().add( "qaz" );
        list.mock().add( "qux" );
        list.mock().add( "qux" );

        list.verify();
    }

    @Test
    public void emptyVariableSequence()
    {
        MockControl<List<String>> list = new MockControl<List<String>>() {};

        list.beginSequence();
        list.allows().add( "foo" );
        list.allows().add( "bar" );
        list.endSequence();

        list.verify();

        list.reset();
        list.mock().add( "foo" );
        list.verify();

        list.reset();
        list.mock().add( "bar" );
        list.verify();

        list.clear();

        list.beginSequence();
        list.allows().add( "foo" );
        list.allows().add( "foo" );
        list.allows().add( "bar" );
        list.expectsAtLeast( 1 ).add( "baz" );
        list.endSequence();

        list.mock().add( "baz" );
        list.mock().add( "baz" );

        list.verify();
    }

    @Test
    public void reluctantVariableSequence()
    {
        MockControl<List<String>> list = new MockControl<List<String>>() {};

        list.beginSequence();
        list.allows().add( "foo" );
        list.expectsOnce().add( "foo" );
        list.endSequence();

        list.mock().add( "foo" );
        list.mock().add( "foo" );

        try
        {
            list.verify();
            throw new RuntimeException( "Expected assertion error" );
        }
        catch( ExpectationFailure ex )
        {
        }

        list.clear();
        list.beginSequence();
        list.expectsOnce().add( "foo" );
        list.allows().add( "foo" );
        list.endSequence();

        list.mock().add( "foo" );
        list.verify();
    }

    @Test( expectedExceptions = ExpectationFailure.class )
    public void badVariableSequence()
    {
        MockControl<List<String>> list = new MockControl<List<String>>() {};

        list.beginSequence();
        list.allows().add( "foo" );
        list.expects( 2 ).add( "bar" );
        list.endSequence();

        list.mock().add( "foo" );
        list.mock().add( "bar" );
        list.mock().add( "foo" );
        list.mock().add( "bar" );

        list.verify();
    }

    @Test( expectedExceptions = ExpectationFailure.class )
    public void completedVariableSequence()
    {
        MockControl<List<String>> list = new MockControl<List<String>>() {};

        list.beginSequence();
        list.allows().add( "foo" );
        list.allows().add( "bar" );
        list.endSequence();

        list.mock().add( "bar" );
        list.mock().add( "foo" );
        list.verify();
    }

    @Test( expectedExceptions = IllegalStateException.class )
    public void unendedSequence()
    {
        MockControl<Map<String, Integer>> map = new MockControl<Map<String, Integer>>() {};
        map.beginSequence();

        map.mock().put( "zero", 0 );
        map.verify();
    }

    @Test
    public void proxyMarker()
    {
        MockControl<GregorianCalendar> calendar = MockControl.of(GregorianCalendar.class);
        assertThat(calendar.mock() instanceof Proxy);
    }

    @Test
    public void expectationInDifferentThread()
    throws Exception
    {
        final MockControl<GregorianCalendar> calendar = MockControl.of(GregorianCalendar.class);
        final Throwable[] error = new Throwable[1];
        Thread thread = new Thread()
        {
            @Override
            public void run()
            {
                try
                {
                    calendar.allows().before(any(Object.class));
                }
                catch (Throwable ex)
                {
                    error[0] = ex;
                }
            }
        };

        thread.start();
        thread.join();

        assertThat(error[0], isA(IllegalStateException.class));
    }
}
